#
#
#   repPlatypus.py  Report Generator in  python
#
#   Copyright (C) 2004
#
#   This library is free software; you can redistribute it and/or
#   modify it under the terms of the GNU Lesser General Public
#   License as published by the Free Software Foundation; either
#   version 2.1 of the License, or (at your option) any later version.
#
#   This library is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#   Lesser General Public License for more details.
#
#   You should have received a copy of the GNU Lesser General Public
#   License along with this library; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

"""Portal forms __init__.
$Id: repPlatypus.py,v 0.1 2004/10/29 20:19:20 ueck Exp $
"""
__version__='$Revision: 0.1 $'[11:-2]

import os
import string
import imp
import getopt, sys
import sql



from reportlab.pdfgen import canvas
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
from reportlab.lib import styles
from reportlab.lib import pagesizes
from reportlab.lib import colors
from types import StringType
from PIL import Image as PIL_Image
from error import errorPrint

# section id's
RH = 'reportheader'
RF = 'reportfooter'
PH = 'pageheader'
PF = 'pagefooter'
GH = 'groupheader'
GF = 'groupfooter'
DH = 'detailheader'
DF = 'detailfooter'
DE = 'detail'
BEGIN = 0
END = 1
WORK = 2
number_types = ('INTEGER','REAL','LONG','DECIMAL','TINY','SHORT','FLOAT','DOUBLE','int','real','money')
########    Globals #############
default_pagesize = pagesizes.A4
# global canvas
canv = None
with_table = 0
# current y-position
current_y = 0
current_x = 0
# sum of sizes of PF, GF,DH,DE,DH
size_sections = 0	# without DE
size_sections_all = 0 # with DE
there_is_report_header = 0
parameters = []	# global copy of all parameters
record = {}	# global copy of current db-record

group_change = {} # global status of group-changes. group_change[column] = 0 -> no change, = 1 this column has changed
group_old_val = {} # global old values of group columns. group_old_val[column] = column-value of old record
g_change = 0  # global value for change of group =0 ->no change, =1 group changed

sum_exists = 0 # =1, if sums are present
sum_group = {}  # sums of groups: sum_group[group] = [column, value]
sum_group_old = {}
num_group = {}  # number of records of this group num_group[group] = value
num_group_old = {}

total_sum = {} # total sum of columns: total_sum[column]= value
num_records = 0 # number of Detail records

key_table = {}  # table of keys:value. key_table{column-name + origin:value}
table_x = []
first_record = 0 # =0 not first record of group or page, =1 first record (used for table painting)
first_page = True # True indicates first page of report
############# functions for dynamic call in parameter of type 'function'
def db_col(col):	# get column of current db-record
	global record
	if record == {} : # if called from xmlreport.build()
		return "0"
	return record[col]

def pagenr(): # gives actual pagenumber
	if canv == None:
		return ""
	page_number = canv.getPageNumber()
	if there_is_report_header:
		page_number -= 1
	return str(page_number)

def date():  # returns date of day: 3. Jan 2005
	from time import strftime, localtime
	return strftime("%d. %b %Y", localtime())

def time():   # returns time : 04:30:42
	from time import strftime, localtime
	return strftime("%H:%M:%S", localtime())


##########################################################################################


def check_param(value):
	global parameters
	# check if value begins with '_' -> insert parameter with this name
	if type(value) != StringType:
		return value
	if value == "":
		return value
	valuel = value.lstrip()
	if valuel[0] == '_':
		# insert parameter with this name
		found = 0
		para_name = valuel[1:]

		for par in parameters:
			if par.name == para_name:
				value = par.value
				found = 1
				break
		if not found:
			errorPrint('EPLA001',(value))
	return value

def get_parameter(parameter):
	global sum_exists, num_group_old
	# read value of parameter and store in parameter.value
	value = parameter.default
	if parameter.origin == "default":
		pass
	elif parameter.origin == "input":
		value = raw_input(parameter.description)
	elif parameter.origin == "command":
		n = parameter.name + "=" # long option of getopt is 'option='
		try:
			(options, arguments) = getopt.getopt(sys.argv[1:], 'f:p:v:o:h:d:u:',[n])
			try:
				for o in options:
					if o[0] == "--" + parameter.name:
						value = o[1]   # value of long option
			except IndexError:
				errorPrint('EPLA002',(parameter.name))

		except getopt.GetoptError:
			errorPrint('EPLA003',(parameter.name,parameter.name))  ##    Error - Abort program
	elif parameter.origin == "function":
		try:
			value = eval(parameter.function)
		except SyntaxError:
			errorPrint('EPLA004',(parameter.name,parameter.function))  ##    Error - Abort program
		parameter.value = value
	elif parameter.origin == "sum":

		sum_exists = 1
		if parameter.column == "" and parameter.group == "":
			# print num_records
			value = num_records
		elif parameter.column == "":  # print number of records for this group
			value = num_group_old[parameter.group]
		if parameter.column != "" and parameter.group != "":
			value = sum_group_old[parameter.group,parameter.column]
		if parameter.column != "" and parameter.group == "":
			value = total_sum[parameter.column]
		parameter.value = value

	if parameter.typ == "int":
	      try:
		parameter.value = int(value)
	      except ValueError:
	      	errorPrint('EPLA005',(parameter.name,value))

	elif parameter.typ == "string":
		parameter.value = value
	elif parameter.typ == "real":
	      try:
		parameter.value = float(value)
	      except ValueError:
		errorPrint('WPLA001',(parameter.name,parameter.value))  # Warning


	return parameter.value

## Classes for parsed xml-page

class xmlReport:
	''' root node of report
	'''

	def _globalInit(self):
		global default_pagesize, canv, with_table, current_y, current_x
		global size_sections, size_sections_all, there_is_report_header
		global parameters, record, group_change, group_old_val, g_change
		global sum_exists, sum_group, sum_group_old, num_group, num_group_old
		global total_sum, num_records, key_table, table_x, first_record
		# initialize all global values
		default_pagesize = pagesizes.A4
		# global canvas
		canv = None
		with_table = 0
		# current y-position
		current_y = 0
		current_x = 0
		# sum of sizes of PF, GF,DH,DE,DH
		size_sections = 0	# without DE
		size_sections_all = 0 # with DE
		there_is_report_header = 0
		parameters = []	# global copy of all parameters
		record = {}	# global copy of current db-record
		group_change = {} # global status of group-changes. group_change[column] = 0 -> no change, = 1 this column has changed
		group_old_val = {} # global old values of group columns. group_old_val[column] = column-value of old record
		g_change = 0  # global value for change of group =0 ->no change, =1 group changed

		sum_exists = 0 # =1, if sums are present
		sum_group = {}  # sums of groups: sum_group[group] = [column, value]
		sum_group_old = {}
		num_group = {}  # number of records of this group num_group[group] = value
		num_group_old = {}

		total_sum = {} # total sum of columns: total_sum[column]= value
		num_records = 0 # number of Detail records

		key_table = {}  # table of keys:value. key_table{column-name + origin:value}
		table_x = []
		first_record = 0 # =0 not first record of group or page, =1 first record (used for table painting)
		first_page = True 
	def __init__(self):
		self.name = None
		self.filename = None
		self.version = None
		self.title = None
		self.author = None
		self.description = None
		self.layout = None
		self.recursive = 1  # recursion allowed for foot-sections
		self.parameters = []
		self.sources = []
		self.groups = []
		self.sections = []
		self._globalInit()  # initailize globals

	def _check_param(self,value):
		global check_param
		# check if value begins with '_' -> insert parameter with this name
		return check_param(value)

	def _build_keys(self, ds):
		global key_table
		# builds the dictionary key_table from key-parameter or db-table
		#print "bauer platypus",ds.sql,ds.keyname,'ende'
		if ds.keyname != '':		# use keytable or keyparameter
			if ds.keyname[0] == "_":
				key_name = ds.keyname[1:]
				found = 0
				for p in self.parameters:
					if p.name == key_name:
						if p.origin != 'key':
							errorPrint('EPLA006',(p.name,ds.name,p.origin))

						else:
							#build dictionary
							hk = string.split(p.default,'|')
							for i in hk:  # i has form: column:key,value
								h2 = string.split(i,':')
								#print "h2",h2[0],h2[1]
								h3 = string.split(h2[1],',')
								#print "h3",h3[0],h3[1]
								key_table[h2[0]+h3[0]] = h3[1]
								#print "key",h2[0]+h3[0],":",h3[1]
							found = 1
							break
				if not found:
					errorPrint('WPLA005',(ds.keyname,ds.name))


			else:
			# find table with this name in database
				if ds.typ == "mysql":
					key_db = sql.MySQL(ds.host,
						ds.user,
						ds.password,
						ds.database,
						table=ds.keyname,
						)
				elif ds.typ == "postgres":
					key_db = sql.Postgresql(ds.host,
						ds.user,
						ds.password,
						ds.database,
						table=ds.keyname,
						)
				elif ds.typ == "CSV":
					key_db = sql.CSV(table=ds.keyname)

				cols = key_db.get_fields().keys()
				if 'attribute' in cols and 'origin' in cols and 'value' in cols:
					rec = key_db.next_row()
					while rec:
						key_table[rec['attribute']+rec['origin']] = rec['value']
						rec = key_db.next_row()
				else:
					errorPrint('WPLA006',(ds.keyname))


	def _check_plaus_datasource(self,ds):
		# abort, if Parameters ar not complete or erronous
		if ds.user == None or ds.user == "" or ds.password == None or ds.password == ""  or ds.database == None or ds.database == "":
			if ds.typ != "CSV":    # CSV-files don't need user,host,password and database values
				errorPrint('EPLA007',(ds.name))

		if ds.sql == "":
			ds.sql = None
		if ds.table == "":
			ds.table = None
		if ds.master == "":
			ds.master = None
		if ds.masterlink == "":
			ds.masterlink = None
		if ds.detaillink == "":
			ds.detaillink = None
		if ds.sort == "":
			ds.sort = None

		if ds.sql == None and ds.table == None:
			errorPrint('EPLA008',(ds.name))

		if ds.master != None and ds.table == None:
				errorPrint('EPLA009',(ds.name))


	def _check_for_unique_names(self,check):
		# abort, if name is missing or multiple in parameters, dataources, sections
		names = []
		for c in check:
			if c.name == None or c.name == "":
				strC= str(c)
				to = string.find(strC,' ',16)
				errorPrint('EPLA010',(strC[16:to]))

			if c.name in names:
				errorPrint('EPLA011',(c.name))

			else:
				names.append(c.name)
	def _check_groups(self):
		# points header and footer to a section?
		global group_change, group_old_val
		group_ok = 1
		s_names = []

		for s in self.sections:
			s_names.append(s.name)
		for g in self.groups:
			group_change[g.column] = 0
			if g.header != "" and g.header not in s_names:
				group_ok = 0
				errorPrint('WPLA002',(g.name))

			if g.footer != "" and g.footer not in s_names:
				group_ok = 0
				errorPrint('WPLA003',(g.name))

			group_old_val[g.column] = ""
			group_change[g.column] = 0
		self._sum_group_zero_fill()
		self._sum_group_old_fill()
		if group_ok == 0:
			sys.exit(1)

	def _check_sections(self,sections):
		global there_is_report_header
		#checks, if sections are more often than one times used
		s={RH:0,RF:0,PH:0,PF:0,GH:0,GF:0,DH:0,DE:0,DF:0}
		for i in sections:
			s[i.typ] +=1
			if i.typ == RH:
				there_is_report_header = 1
		for l in s.items():
			if l[0] not in [GH,GF] and  l[1] > 1:
				errorPrint('EPLA012',(l[0]))


	def _check_parameters(self):
		global total_sum, number_types
		g_i = []
		for g in self.groups:
			g_i.append( g.name)  # test, if group of parameter is a group
		# seek Datasource of section DE and control, if column-type is in number_types
		s = self._get_section(DE)
		cols = s.ds.db.get_fields()
		for p in self.parameters:
			if p.origin == 'sum':
				if p.group != "" and p.group not in g_i:# groupname in parameter wrong
					errorPrint('EPLA013',(p.name, p.group))

				if p.column != "" :
					if p.column[0] == '_':   # column is parameter, not in db
						pass
					elif cols[p.column] not in number_types: # test, if type of db-column is numeric
						errorPrint('EPLA014',(p.name, p.column))

				if p.column != "" and p.group == "":
					total_sum[p.column] = 0


	def _get_section_with_name(self, name):
		# returns section with name
		for s in self.sections:
			if s.name == name:
				return s
		return None

	def _build_table_lines(self):
		# tests, if table-parameter is set in Detail Section and sets beginpoints of columns
		global with_table,canv, table_x
		found = 0
		for s in self.sections:
			if s.typ == DE:
				found =1
				break
		if found and s.tableform == 1:    # test, if tableform is set in detail-section
			field_len = s.ds.db.get_fieldlen()
			with_table = 1
			current_x = 2
			field_found = False
			for f in s.fields:
				if isinstance(f,xmlField) : # table only for fields, not lines etc.
					if f.x == 0:
						f.x = current_x    # no x-coordinate of field -> create it
					else:
						current_x = f.x-2    # take x-coordinate of field for vertical line
					table_x.append(current_x)
					if f.length > 0: # compute length of output string
						leng = f.length
					else:
						if f.text != "":   # Textfield or parameter
							if f.text[0] == '_':
								errorPrint('WPLA004',(f.name,len(f.text)))

							leng = canv.stringWidth(f.text, f.font, f.fontsize)

						if f.column != "":
							testText = 'M'*field_len[f.column]
							leng = canv.stringWidth(testText, f.font, f.fontsize)
					f.laen = leng
					current_x += leng
					field_found = True
			if field_found: table_x.append(current_x)






	def _check_columns(self):
		# check, if columns fit to datasources
		for s in self.sections:
			if s.source != "":  # datasources used
				s.get_datasource(self.sources)
				ds = s.ds
				if ds == None:
					errorPrint('EPLA015',(s.source,s.name))

				for f in s.fields:
					if isinstance(f,xmlField):
						if f.column !="": # column is used
							col_names = ds.cols
							if f.column[0] == "_":
								col = f.column[1:]
							else:
								 col = f.column
							if col not in col_names:
								errorPrint('EPLA016',(col))

		for g in self.groups:
			# check, if group and header have the same datasource and columns exist
			header = self._get_section_with_name(g.header)
			footer = self._get_section_with_name(g.footer)
			if header == None and footer == None:
				errorPrint('EPLA017',(g.name))

			if header == None:
				footer_source = footer.source
			else:
				 footer_source = None
			if footer == None:
				header_source = header.source
			else:
				header_source = None
			if header_source != None and footer_source != None and header_source != footer_source:
				errorPrint('EPLA018',(head_source))

			if g.column not in col_names:
				errorPrint('EPLA019',(g.column,ds.name))


	def _init_datasource(self, ds, db_data):
		# initalize all datasources
			global parameters
			if len(db_data) != 0: # take arguments from commandline
				try:
					ds.host = db_data['host']
				except KeyError:
					pass
				try:
					ds.user = db_data['user']
				except KeyError:
					pass
				try:
					ds.password = db_data['password']
				except KeyError:
					pass
				try:
					ds.database = db_data['database']
				except KeyError:
					pass
			parameters = self.parameters
			ds.host = self._check_param(ds.host)
			ds.user = self._check_param(ds.user)
			ds.password = self._check_param(ds.password)
			ds.database = self._check_param(ds.database)
			ds.table = self._check_param(ds.table)
			ds.sort = self._check_param(ds.sort)
			ds.master = self._check_param(ds.master)
			ds.masterlink = self._check_param(ds.masterlink)
			ds.detaillink = self._check_param(ds.detaillink)
			ds.sql = self._check_param(ds.sql)
			self._check_plaus_datasource(ds)
			if ds.typ == "mysql":
				ds.db = sql.MySQL(ds.host,
						ds.user,
						ds.password,
						ds.database,
						table=ds.table,
						sort=ds.sort,
						master=ds.master,
						masterlink=ds.masterlink,
						detaillink=ds.detaillink,
						sql=ds.sql)
			elif ds.typ == "CSV":
                		ds.db = sql.CSV(ds.table)
			elif ds.typ == "postgres":
				ds.db = sql.Postgresql(ds.host,
						ds.user,
						ds.password,
						ds.database,
						table=ds.table,
						sort=ds.sort,
						master=ds.master,
						masterlink=ds.masterlink,
						detaillink=ds.detaillink,
						sql=ds.sql)
			ds.cols = ds.db.get_fields().keys()
			ds.num_rows = ds.db.get_numrows()
			self._build_keys(ds)

	def _get_section(self,section_typ):

		for s in self.sections:
			if s.typ == section_typ:
				return s
		return None

	def _sum_group_old_fill(self):
		# fills group sums
		global group_change, sum_group, num_group
		global sum_group_old,  num_group_old

		for g in self.groups:
			if group_change[g.column]:
				for p in self.parameters:
					if p.group == g.name and p.column != "":
						sum_group_old[p.group,p.column] = sum_group[p.group,p.column]
						#print "old_fill"
					if p.group == g.name and p.column == "":
						num_group_old[p.group] = num_group[p.group]

	def _sum_group_add(self):
		# fills group sums
		global sum_group,  num_group
		global sum_group_old,  num_group_old
		global get_parameter,record

		for p in self.parameters:
			if p.group != "" and p.column != "" and p.origin == 'sum' :
				if p.column[0] == '_': #column is parameter
					# compute parameter and add
					pParName = p.column[1:]
					for p1 in self.parameters:
						if pParName == p1.name:
							v = get_parameter(p1)
					sum_group[p.group, p.column] += float(v)

				else:
					sum_group[p.group, p.column] += float(record[p.column])
					#print "add: %s" % sum_group[p.group,p.column]
					#print "value= %s " % record[p.column]
			if p.group != "" and p.column == "" and p.origin == 'sum':
				# count records/group
				num_group[p.group] += 1

	def _sum_group_zero_fill(self):
		# fills group sums
		global group_change, sum_group,  num_group
		global sum_group_old,  num_group_old
		for g in self.groups:
			if not num_group.has_key(g.name): # first call -> initialize fields
				num_group[g.name] =0
				for p in self.parameters:
					if p.group == g.name and p.column != "":
						# sum of column for a group
						#print g.name + " col="+p.column
						sum_group_old[p.group,p.column] = 0
						sum_group[p.group,p.column] = 0
					if p.group == g.name and p.column == "":
						 # for number of records of group
						num_group_old[p.group] = 0
						num_group[p.group] = 1
			else:
				if group_change[g.column] :
					for p in self.parameters:
						if p.group == g.name and p.column != "":
							sum_group[p.group,p.column] = 0
							#print "zero_fill records of group %s %s" % (g.name, p.column)
						if p.group == g.name and p.column == "":
							num_group[g.name] = 0

	def _group_old_fill(self):
		# fill group_old_val with values of record
		global group_old_val
		global record
		for g in self.groups:
			group_old_val[g.column] = record[g.column ]


	def _check_group_change(self):
		# checks, if group has changed. Sets global group_change
		global group_change
		global group_old_val
		global record
		global g_change
		for g in self.groups:	# set group_change to unchanged(=0)
			group_change[g.column] = 0
		g_change = 0
		for g in self.groups:
			if group_old_val[g.column] != record[g.column]:
				# group has changed
				group_change[g.column] = 1
				g_change = 1
			if g_change == 1:
				group_change[g.column] = 1 # group change for all columns under the changed
				#print "old_val= %s  " % group_old_val[g.column]

	def _add_record(self):
		# add 1 to record number and values to total_sum[column]
		global num_records, total_sum
		num_records +=1
		for p in parameters:
			if p.group =="" and p.column != "" and p.origin == "sum":

				total_sum[p.column] += record[p.column]

	def _nextrow(self,section):
		global record, g_change, group_change, num_records
		# reads next record from datasource
		if len(record)!= 0 :
			self._group_old_fill()
			section.ds.record_old = section.ds.record # stores record for group-values
		section.ds.record = section.ds.db.next_row()
		if len(record) == 0:
			section.ds.record_old = section.ds.record # stores record for group-values
			self._sum_group_zero_fill()
		record = section.ds.record
		if section.ds.record == None:
			# make all group changes true
			g_change = 1
			for g in self.groups:
				group_change[g.column] = 1
			return 0
		self._add_record()
		self._check_group_change()
		return 1

	def _newPage(self):
		global canv, current_y, first_record
		# write canvas and make a new Page
		canv.showPage()
		#canv.translate(self.layout.leftmargin,self.layout.height - current_y )
		canv.setPageSize((self.layout.width,self.layout.height))
		canv.translate(self.layout.leftmargin,self.layout.height - self.layout.topmargin)
		current_y = self.layout.height - self.layout.topmargin
		first_record = 1

	def _get_sizeSections(self):
		s = []
		sum = 0
		s.append(self._get_section(PF))
		s.append(self._get_section(DH))
		s.append(self._get_section(DF))
		for i in s:
			if i != None:
				sum += i.size
		size_de = self._get_section(DE).size + sum + 2
		return  (size_de, sum)

	def _get_sizeGroups(self):
		global group_change
		self.s_header = []
		self.s_footer = []
		for g in self.groups:
			if group_change[g.column] == 1 :
				if g.header != "":
					self.s_header.append(self._get_section_with_name(g.header))
				if g.footer != "":
					self.s_footer.append(self._get_section_with_name(g.footer))
		sum_header = 0
		sum_footer = 0
		for g in self.s_header:
			if g != None:
				sum_header += g.size
		for g in self.s_footer:
			if g != None:
				sum_footer += g.size
		s = []
		sum = 0
		s.append(self._get_section(DH))
		s.append(self._get_section(DE))
		s.append(self._get_section(DF))
		for i in s:
			if i != None:
				sum += i.size

		sum_Dx = sum_header + sum
		return (sum_footer, sum_header, sum_Dx) # returns Tupel with size of all actual footers, headers and headers + Detail



	def _exec_group_section(self, typus):
		global current_y, g_change
		# prints all actual group sections
		if not g_change:
			return # no group change occurred
		size_GF, size_GH, size_GH_Dx = self._get_sizeGroups()
		if typus in [END, WORK]:
			if size_GF > (current_y-self.layout.bottommargin):
				self._exec_section(PF)
				self._exec_section(PH)
			for s in self.s_footer:
				s.drawOn()

		if typus in [BEGIN, WORK]:
			if size_GH_Dx > (current_y-self.layout.bottommargin):
				self._exec_section(PF)
				self._exec_section(PH)
			for s in self.s_header:
				s.drawOn()
			first_record = 1




	def _exec_section(self,section_id):
		global size_sections, size_sections_all, current_y, g_change, first_page
		s = self._get_section(section_id)
		if s != None:
			if section_id in [RH, RF]:		# Reihenfolge nicht aendern !!
				s.size = self.layout.height - self.layout.topmargin - self.layout.bottommargin
			if section_id in [RF,PH]:
				if first_page:
					first_page = False
				else:
					self._newPage()
			if section_id in [DE]:
				# check, if new page is needed

				if size_sections_all > (current_y-self.layout.bottommargin)   or (s.newpage != 0):
				  # page too short or new page in section indicated
					if self.recursive:
						self.recursive = 0
						self._exec_section(PF)
						self._exec_section(PH)
						self.recursive = 1
			if section_id == PF:
				help = current_y - self.layout.bottommargin  - size_sections
				canv.translate(0,-help)
				current_y -= help

			if section_id == DE:
				self._exec_section(DH)
				first_record = 1
			#####    write section
			s.drawOn()
			#####
			if section_id == DE:
				self._exec_section(DF)
				row = self._nextrow(s)
				if not row:
					self._sum_group_old_fill()
					self._exec_group_section(END)
					return 0	# no more data records
				else:
					self._sum_group_old_fill()
					self._exec_group_section(WORK)
					self._sum_group_zero_fill()
					self._sum_group_add()
					return 1
			return 0

	def build(self,db_data):
		global canv,current_y, size_sections,size_sections_all, parameters
		global get_parameter
		"""Build the report.
		   If the filename argument is provided in db_data, then that filename is used
		   rather than the one provided upon initialization.
		   ).
		"""

		# Parameter abarbeiten
		self.filename = db_data.get('filename',self.filename)
		self._check_for_unique_names(self.parameters)
		self._check_for_unique_names(self.sources)
		self._check_for_unique_names(self.groups)
		self._check_for_unique_names(self.sections)
		self._check_sections(self.sections)
		self._check_groups()
		# open Datasources
		for ds in self.sources:
			self._init_datasource(ds,db_data)
		if len(self.sources) > 0:
			self._check_columns()
		self._check_parameters()
		for par in self.parameters:
			get_parameter(par)
		parameters = self.parameters

		if self.filename == "": # send output to stdout (for HTML or CGI)
			canv = canvas.Canvas(sys.stdout)
		else:
			canv = canvas.Canvas(self.filename)
		self._build_table_lines()
		canv.setTitle(self.title or '')
		canv.setAuthor(self.author or 'unknown')
		canv.setSubject(self.name or '')
		canv.setPageCompression(1)
		self.layout.create()
		canv.setPageSize((self.layout.width,self.layout.height))
		current_y = self.layout.height-self.layout.topmargin
		canv.translate(self.layout.leftmargin,self.layout.height -self.layout.topmargin)
		for s in self.sections: # calculate size if sections
			s.calc_size()
			s.get_datasource(self.sources)
		size_sections_all, size_sections = self._get_sizeSections()
		cont = self._nextrow(self._get_section(DE))
		self._sum_group_zero_fill()
		self._sum_group_add()
		self._exec_section(RH)
		self._exec_section(PH)
		self._exec_group_section(BEGIN)
		while cont :
			cont = self._exec_section(DE)
		self._exec_section(PF)
		self._exec_section(RF)
		canv.showPage()
		canv.save()

class xmlParameter:
	''' node for parameters
	'''

	def __init__(self):
		self.name = None
		self.required = 0
		self.limited = 0
		self.default = None
		self.description = None
		self.origin = None
		self.typ = None
		self.function = None
		self.column = None
		self.group = None
		self.value= None

	def create(self,parameter):
		parameter.name = self.name
		parameter.required = self.required
		parameter.limited = self.limited
		parameter.default = self.default
		parameter.description = self.description
		parameter.origin = self.origin
		parameter.typ = self.typ
		parameter.function = self.function
		parameter.column = self.column
		parameter.group = self.group

class xmlDatasource:
	''' node for datasource
	'''
	def __init__(self):
		self.name = None
		self.host = None
		self.user = None
		self.password = None
		self.table = None
		self.database = None
		self.master = None
		self.masterlink = None
		self.detaillink = None
		self.sort = None
		self.typ = "mysql"
		self.sql = None
		self.record= {}
		self.record_old = {}
		self.keyname = None

	def create(self,datasource):
		datasource.name = self.name
		datasource.host = self.host
		datasource.user = self.user
		datasource.password = self.password
		datasource.table = self.table
		datasource.database = self.database
		datasource.master = self.master
		datasource.masterlink = self.masterlink
		datasource.detaillink = self.detaillink
		datasource.sort = self.sort
		datasource.typ = self.typ
		datasource.sql = self.sql
		datasource.record = self.record
		datasource.record_old = self.record_old
		datasource.keyname = self.keyname

class xmlGroup:
	''' node for datasource
	'''
	def __init__(self):
		self.name = None

		self.column = None
		self.header = None
		self.footer = None

	def create(self,group):
		group.name = self.name
		group.column = self.column
		group.header = self.header
		group.footer = self.footer





## Template related


class xmlLayout:
	def __init__(self):
		self.pagesize = default_pagesize
		self.landscape = 0
		self.showboundary = 0
		self.leftmargin=10
		self.rightmargin=10
		self.topmargin=10
		self.bottommargin=10
		self.allowsplitting=1
		self.pagesequence = 'all'


	def create(self):
		global real_width, real_height
		ps = getattr(pagesizes,self.pagesize,default_pagesize)
		if self.landscape:
			ps = pagesizes.landscape(ps)
		self.width,self.height = ps
		real_width = self.width - self.leftmargin -self.rightmargin
		real_height = self.height -self.topmargin -self.bottommargin


class xmlSection:
	def __init__(self):
		self.name = None
		self.description = None
		self.typ = 'detail'
		self.size = 0
		self.source = ''
		self.bgcolor = (1,1,1)
		self.alternate = 0
		self.bg_alt = 0
		self.fields = []
		self.ds = None
		self.tableform = 0
		self.newpage = 0

	def get_datasource(self, datasources):
		# get db-handle of datasource with given name
		if self.source == "":
			self.db = None
		for ds in datasources:
			if ds.name == self.source:
				self.ds = ds



	def drawOn(self):
		global current_y, canv, real_width, with_table, table_x, first_record
		# calculate bottom-position of section
		current_y = current_y - self.size
		canv.translate(0,-self.size)
		if self.bgcolor != None:
			if self.alternate:   # bgcolor changes always
				self.bg_alt = not self.bg_alt
			if not self.bg_alt :
				r,g,b = self.bgcolor
			else:
				r,g,b = (1,1,1)
			canv.setFillColorRGB(r,g,b)
			canv.setStrokeColorRGB(r,g,b)
			if with_table and self.typ == DE:
				canv.rect(0,-1, table_x[len(table_x)-1], self.size,stroke=1,fill=1)
			else:
				canv.rect(0,-1,real_width, self.size,stroke=1,fill=1)
		i = 0
		le = len(table_x)-1
		if first_record and with_table and self.typ == DE:	# draw table lines: horizontal line at begin
			line= xmlLine(table_x[0]-2,self.size ,table_x[le]+2,self.size)
			line.strokeColor = (0,0,0)
			line.drawOn(self.ds)

		for field in self.fields:
			if with_table and self.typ == DE and isinstance(field,xmlField):  # table is only for xmlField
	                	line= xmlLine(table_x[i]-2,0,table_x[i]-2,self.size)
				i +=1
				line.strokeColor = (0,0,0)
				line.drawOn(self.ds)
			# print field data
			field.drawOn(self.ds)

		if with_table and self.typ == DE:
			line = xmlLine(table_x[0]-2,0,table_x[le]+2,0) # horizontal line at bottom
			line.strokeColor = (0,0,0)
			line.drawOn(self.ds)
                	line= xmlLine(table_x[le]+2,0,table_x[le]+2,self.size)  # vertical line at end of line
			line.strokeColor = (0,0,0)
			line.drawOn(self.ds)


	def calc_size(self):
		max_size = 0
		for field in self.fields:
			if isinstance(field,xmlField):
				if field.typ == "string":
					if field.height != 0:
			        		field_h = field.height
					else:
						field_h = field.fontsize
					field_all = field.y + field_h
					if max_size <= field_all:
						max_size = field_all
			else:
				max_size = max(max_size,field.size)
		self.size = max_size

class xmlField:
	def __init__(self, x, y):
		self.name = None
		self.text = ''
		self.x = x
		self.y = y
		self.length = 0
		self.height = 0
		self.align = TA_LEFT
		self.font = 'Times-Roman'
		self.size=self.fontsize = 12
		self.color = (0,0,0)
		self.showboundary = 0
		self.column = None
		self.typ = None
		self.key = 0
		self.laen = 0   # length of field in points computed in build_table_lines for db-fields



	def drawOn(self,datasource):
		global  get_param, parameters, key_table, table_x
		if self.text != "":
			drawText = self.text
			t = self.text.lstrip()
			if t[0] =="_":     # parameter found
				para_name = t[1:]
				found = 0
				for par in parameters:
					if par.name == para_name:
						found = 1
						break
				if not found:
					errorPrint('EPLA020',(self.text))

				drawText = str(get_parameter(par))


		if self.color is None:
			return
		# construct drawText from datasource
		if self.column != "":
			if self.column[0] == "_":
				drawText = datasource.record_old[self.column[1:]] # old value of column (name preceded by _)
				c_help = self.column[1:]
			else:
				drawText = datasource.record[self.column] # normal db-value of column
				c_help = self.column
			if self.key == '1':     # value should be transformed
				drawText = key_table[c_help + drawText]

		if self.typ == 'real' or self.typ == "money":
			drawText = "%.2f" % float(drawText)
		drawText = str(drawText)
		ltext = len(drawText)		# self.text sized to length of self.length (cut or widened)
		mSize =canv.stringWidth("b", self.font, self.fontsize)
		help_len = int(self.length / mSize)   # help_len is possible length of field in characters
		if help_len == 0:
			help_len = ltext
		if ltext > help_len:
			drawText = drawText[:help_len]
		else:
			if self.typ in number_types:
				drawText =  " "*(help_len - ltext) + drawText # numbers are right justified
			else:
				drawText = drawText + " "*(help_len - ltext)

		canv.setFont(self.font, self.fontsize)
		r,g,b = self.color
		canv.setFillColorRGB(r,g,b)
		cur_y = self.y
		leng = canv.stringWidth(drawText, self.font, self.fontsize) # compute length of drawText
		if self.align == TA_LEFT:
			canv.drawString(self.x, cur_y+2, drawText)
		elif self.align == TA_CENTER:
			pos = self.x + leng/2
			canv.drawCentredString(pos, cur_y+2, drawText)
		elif self.align == TA_RIGHT:
			if self.laen != 0:
				pos = self.x + self.laen -2
			else:
				pos = self.x + self.length -leng
			canv.drawRightString(pos, cur_y+2, drawText)



class xmlFixedImage:
	"""You place this on the page, rather than flowing it"""
	def __init__(self):
		self.name = None
		self.filename = None
		self.x = 0
		self.y = 0
		self.width = None
		self.height = None

	def drawOn(self, ds):
		global canv,sys
		if self.filename:
			canv.drawInlineImage(
					    self.filename,
					    self.x,
					    self.y,
					    self.width,
					    self.height
					       )


class xmlRectangle:
	def __init__(self, x, y, width, height):
		self.x = x
		self.y = y
		self.width = width
		self.height = height
		self.fillColor = None
		self.strokeColor = (1,1,1)
		self.lineWidth=0

	def drawOn(self, ds):
		global canv
		canv.saveState()
		canv.setLineWidth(self.lineWidth)
		if self.fillColor:
			r,g,b = self.fillColor
			canv.setFillColorRGB(r,g,b)
		if self.strokeColor:
			r,g,b = self.strokeColor
			canv.setStrokeColorRGB(r,g,b)
		canv.rect(self.x, self.y, self.width, self.height,
			stroke=(self.strokeColor<>None),
			fill = (self.fillColor<>None)
			)
		canv.restoreState()

class xmlRoundRect:
	def __init__(self, x, y, width, height, radius):
		self.x = x
		self.y = y
		self.width = width
		self.height = height
		self.radius = radius
		self.fillColor = None
		self.strokeColor = (1,1,1)
		self.lineWidth=0

	def drawOn(self, ds):
		global canv
		canv.saveState()
		canv.setLineWidth(self.lineWidth)
		if self.fillColor:
			r,g,b = self.fillColor
			canv.setFillColorRGB(r,g,b)
		if self.strokeColor:
			r,g,b = self.strokeColor
			canv.setStrokeColorRGB(r,g,b)
		canv.roundRect(self.x, self.y, self.width, self.height,
			self.radius,
			stroke=(self.strokeColor<>None),
			fill = (self.fillColor<>None)
			)
		canv.restoreState()

class xmlLine:
	def __init__(self, x1, y1, x2, y2):
		self.x1 = x1
		self.y1 = y1
		self.x2 = x2
		self.y2 = y2
		self.fillColor = None
		self.strokeColor = (1,1,1)
		self.lineWidth=0


	def drawOn(self, ds):
		global canv
		canv.saveState()
		canv.setLineWidth(self.lineWidth)
		if self.strokeColor:
			r,g,b = self.strokeColor
			canv.setStrokeColorRGB(r,g,b)
		canv.line(self.x1, self.y1, self.x2, self.y2)
		canv.restoreState()

class xmlEllipse:
	def __init__(self, x1, y1, x2, y2):
		self.x1 = x1
		self.y1 = y1
		self.x2 = x2
		self.y2 = y2
		self.fillColor = None
		self.strokeColor = (1,1,1)
		self.lineWidth=0


	def drawOn(self, ds):
		global canv
		canv.saveState()
		canv.setLineWidth(self.lineWidth)
		if self.strokeColor:
			r,g,b = self.strokeColor
			canv.setStrokeColorRGB(r,g,b)
		if self.fillColor:
			r,g,b = self.fillColor
			canv.setFillColorRGB(r,g,b)
		canv.ellipse(self.x1, self.y1, self.x2, self.y2,
			stroke=(self.strokeColor<>None),
			fill = (self.fillColor<>None)
			)
		canv.restoreState()

class xmlPolygon:
	def __init__(self, pointlist):
		self.points = pointlist
		self.fillColor = None
		self.strokeColor = (1,1,1)
		self.lineWidth=0


	def drawOn(self, ds):
		global canv
		canv.saveState()
		canv.setLineWidth(self.lineWidth)
		if self.strokeColor:
			r,g,b = self.strokeColor
			canv.setStrokeColorRGB(r,g,b)
		if self.fillColor:
			r,g,b = self.fillColor
			canv.setFillColorRGB(r,g,b)

		path = canv.beginPath()
		(x,y) = self.points[0]
		path.moveTo(x,y)
		for (x,y) in self.points[1:]:
			path.lineTo(x,y)
		path.close()
		canv.drawPath(path, stroke=(self.strokeColor<>None),fill=(self.fillColor<>None))
		canv.restoreState()

